﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OauthProvider
{
    public class OAuthProvider : IOAuthProvider
    {
        /*
         * Inspector to check request and Tokenstore to create and manage token
         * */
        readonly List<IContextInspector> _inspectors = new List<IContextInspector>();
        readonly ITokenStore _tokenStore;

        public bool RequiresCallbackUrlInRequest { get; set; }

        public OAuthProvider(ITokenStore tokenStore, params IContextInspector[] inspectors)
        {
            RequiresCallbackUrlInRequest = true;
            //Check store and inspectors argument avaiable
            if (tokenStore == null) throw new ArgumentNullException("TokenStore");
            _tokenStore = tokenStore;
            if (inspectors != null) _inspectors.AddRange(inspectors);
        }

        public void AddInspector(IContextInspector inspector)
        {
            _inspectors.Add(inspector);
        }

        /*
         * Produce RequestToken for request from consumer(request is analyse to IOauthContext)          */
        public virtual IToken GrantRequestToken(IOAuthContext context)
        {
            //Check request contain token
            AssertContextDoesNotIncludeToken(context);
            //Check request have full required argument and valid
            InspectRequest(ProviderPhase.GrantRequestToken, context);

            return _tokenStore.CreateRequestToken(context);
        }

        /*
         * Exchange RequestToken to AccessToken which can access protected resource with
         * */
        public virtual IToken ExchangeRequestTokenForAccessToken(IOAuthContext context)
        {
            //Check request have full required argument and valid to exchange
            InspectRequest(ProviderPhase.ExchangeRequestTokenForAccessToken, context);
            //Update RequestToken and check status to produce a AccessToken
            _tokenStore.ConsumeRequestToken(context);

            switch (_tokenStore.GetStatusOfRequestForAccess(context))
            {
                case RequestForAccessStatus.Granted:
                    break;
                case RequestForAccessStatus.Unknown:
                    throw Error.ConsumerHasNotBeenGrantedAccessYet(context);
                default:
                    throw Error.ConsumerHasBeenDeniedAccess(context);
            }

            return _tokenStore.GetAccessTokenAssociatedWithRequestToken(context);
        }

        public virtual bool RefreshAccessToken(IOAuthContext context)
        {
            //Check request have full required argument and valid to exchange
            InspectRequest(ProviderPhase.RefreshAccessToken, context);

            //Update RequestToken and check status to produce a AccessToken
            return _tokenStore.RefreshToken(context);
        }

        public virtual void AccessProtectedResourceRequest(IOAuthContext context)
        {
            InspectRequest(ProviderPhase.AccessProtectedResourceRequest, context);

            _tokenStore.ConsumeAccessToken(context);
        }

        /*
         *  Check request have full required argument and valid to do Action
         * */
        protected virtual void InspectRequest(ProviderPhase phase, IOAuthContext context)
        {
            AssertContextDoesNotIncludeTokenSecret(context);

            AddStoredTokenSecretToContext(context, phase);
            if (phase != ProviderPhase.RefreshAccessToken)

                ApplyInspectors(context, phase);
        }

        /*
         *  Check request with all inspectors
         * */
        void ApplyInspectors(IOAuthContext context, ProviderPhase phase)
        {
            foreach (IContextInspector ist in _inspectors)
            {
                ist.InspectContext(phase, context);
            }
        }

        /*
         *  Add TokenSecrect for Token (using to verifying again later)
         * */
        void AddStoredTokenSecretToContext(IOAuthContext context, ProviderPhase phase)
        {
            if (phase == ProviderPhase.ExchangeRequestTokenForAccessToken)
            {
                string secret = _tokenStore.GetRequestTokenSecret(context);

                context.TokenSecret = secret;
            }

            else if (phase == ProviderPhase.AccessProtectedResourceRequest || phase == ProviderPhase.RefreshAccessToken)
            {
                string secret = _tokenStore.GetAccessTokenSecret(context);

                context.TokenSecret = secret;
            }
        }

        void AssertContextDoesNotIncludeToken(IOAuthContext context)
        {
            if (context.Token != null)
            {
                throw Error.RequestForTokenMustNotIncludeTokenInContext(context);
            }
        }

        static void AssertContextDoesNotIncludeTokenSecret(IOAuthContext context)
        {
            if (!string.IsNullOrEmpty(context.TokenSecret))
            {
                throw Error.RequestForTokenMustNotIncludeTokenSecrectInContext(context);
            }
        }
    }
}
